/************************************************************************

							Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  							   memory_buffer.cpp

	$Header: /resource_editor/memory_buffer.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "memory_buffer.h"

#include "sized_int_types.h"
#include "streambuf_operators.h"

int const k_buffer_size = 65536;

/////////////////////////////////////////////////////////////////////////////
// stream buf related class, which like stringbuffer goes to memory, which
// uses efficient allocation of buffers

t_memory_buffer::t_memory_buffer()
{
	m_buffer_size_default = k_buffer_size;

	m_buffer.resize( m_buffer_size_default );
	m_buffer_read.resize( m_buffer_size_default );

	reset_get_stream_ptrs(m_buffer_size_default);
	reset_put_stream_ptrs();

	m_current_block = m_buffers.begin();
	m_is_merged = false;
}

t_memory_buffer::t_memory_buffer( int request_mem_size )
{
	m_buffer_size_default = request_mem_size;

	m_buffer.resize( m_buffer_size_default );
	m_buffer_read.resize( m_buffer_size_default );

	reset_get_stream_ptrs(m_buffer_size_default);
	reset_put_stream_ptrs();

	m_current_block = m_buffers.begin();
	m_is_merged = false;
}	


t_memory_buffer::t_memory_buffer( t_memory_buffer * copy_buffer )
{
	if (copy_buffer != NULL)
	{
		std::list<std::string>::const_iterator index_iter;
		
		for ( index_iter = copy_buffer->m_buffers.begin(); index_iter != copy_buffer->m_buffers.end(); ++index_iter )
		{
			m_buffers.push_back(*index_iter);
		}
		
		m_buffer_size_default = copy_buffer->m_buffer_size_default;
	}
	else
	{
		m_buffer_size_default = k_buffer_size;
	}
	
	m_buffer.resize( m_buffer_size_default );
	m_buffer_read.resize( m_buffer_size_default );

	reset_get_stream_ptrs(m_buffer_size_default);
	reset_put_stream_ptrs();

	m_current_block = m_buffers.begin();
	m_is_merged = false;
	
	return;
}


// stream buf related class, which like stringbuffer goes to memory, which
// uses efficient allocation of buffers
t_memory_buffer::~t_memory_buffer()
{
}

// stream buf related class, which like stringbuffer goes to memory, which
// uses efficient allocation of buffers
std::string t_memory_buffer::copy_buffer()
{
	std::list<std::string>::iterator index;
	std::string                      result;
	int                              size = 0;
	int                              pending;

	pending = pptr() - &(*m_buffer.begin());
	size = pending;

	for ( index = m_buffers.begin(); index != m_buffers.end(); index++)
		size += index->size();

	result.resize( size );

	char* dest = &(*result.begin());

	for ( index = m_buffers.begin(); index != m_buffers.end(); index++)
	{
		memcpy( dest, &(*index->begin()), index->size() );
		dest += index->size();
	}

	memcpy( dest, &(*m_buffer.begin()), pending );

	return result;
}

// stream buf related class, which like stringbuffer goes to memory, which
// uses efficient allocation of buffers
void t_memory_buffer::flush_buffer()
{
	// int pending = pptr() - &(*m_buffer.begin());
  int pending = (int)pptr();
  if(m_buffer.size() > 0) pending -= (int)&(*m_buffer.begin());
	
	if (pending == 0)
	{
		//a clear_buffer() or compact() compacts the buffers to 0, need to re-expand them if were
		//writing again.

		if (m_buffer.size() == 0)
		{
			m_buffer.resize( m_buffer_size_default );
			reset_put_stream_ptrs();
		}

		return;
	}

	if (pending == m_buffer_size_default)
	{
		m_buffers.push_back( m_buffer );

		m_buffer = "";
		m_buffer.resize( m_buffer_size_default );
	}
	else
	{
		std::string buffer;

		buffer.resize( pending );
		memcpy( &(*buffer.begin()), &(*m_buffer.begin()), pending );
		m_buffers.push_back( buffer );
	}
	
	if (m_buffers.size() > 1)
		m_is_merged = false;

	// Reset the streambuf input buffer

	reset_put_stream_ptrs();
}

// stream buf related class, which like stringbuffer goes to memory, which
// uses efficient allocation of buffers
std::streambuf::int_type t_memory_buffer::overflow( int_type c )
{
	if ( c == traits_type::eof() )
		return traits_type::not_eof( c );

	flush_buffer();

	// Put the char in the buffer.
	*m_buffer.begin() = traits_type::to_char_type( c );

	pbump( 1 );

	return traits_type::not_eof( c );
}

int t_memory_buffer::size()
{
	int total_size = 0;
	
	std::list<std::string>::const_iterator index_iter;
	
	for ( index_iter = m_buffers.begin(); index_iter != m_buffers.end(); ++index_iter )
	{
		total_size += index_iter->size();
	}

	total_size += pptr() - &(*m_buffer.begin());

	return total_size;
}

////////////////////////////////////////////////////////////////
//clear_buffer :  clears all the buffers

void t_memory_buffer::clear_buffer()
{
	compact();

	m_buffers.resize(0);
}

////////////////////////////////////////////////////////////////
//compact :  removes the current string buffer m_buffer, reduces excessive memory 
//used for stored memory_buffers.

void t_memory_buffer::compact()
{
	flush_buffer();

	m_buffer = "";
	m_buffer.resize( 0 );
	
	m_buffer_read = "";
	m_buffer_read.resize( 0 );

	// Reset the streambuf buffers

	reset_get_stream_ptrs(0);
	reset_put_stream_ptrs();
}

////////////////////////////////////////////////////////////////
//merge_buffers_for_read : merges the buffer segements into a unified buffer for random access

void t_memory_buffer::merge_buffers_for_read()
{
	compact();
	
	//early out if m_buffers already meet the conditions
	if (m_buffers.size() == 1)
	{
		reset_for_read();
		
		m_is_merged = true;

		return;
	}

	std::string compact_buffer = copy_buffer();

	int size = compact_buffer.size();

	clear_buffer();

	m_buffers.push_back(compact_buffer);
	
	reset_for_read();

	m_is_merged = true;
}

t_memory_buffer::pos_type	t_memory_buffer::seekoff(off_type offset, std::ios_base::seekdir base_offset, std::ios_base::openmode which )
{
	//this function is only valid for a merged memory buffers.
	if (m_is_merged == false) 
		return -1;

	//unsupported for in streams, only out is supported
	if ((which & std::ios_base::out) == 0) 
		return -1;

	t_uint32 position;
	t_uint32 buffer_size		= m_buffer_read.size();

	switch (base_offset)
	{
		case std::ios_base::beg:
			position = offset;
			break;

		case std::ios_base::cur:
			position = (gptr() - m_buffer_read.data()) + offset;
			break;

		case std::ios_base::end:
			position = buffer_size + offset;
			break;
	}

	if (position < 0 || position > buffer_size)
		return -1;
	
	// setg( m_buffer_read.begin(), m_buffer_read.begin()+position, m_buffer_read.end());
  if(m_buffer_read.size() > 0)
  {
    char*s0 = &(*m_buffer_read.begin());
    char*s1 = s0 + position;
    char*s2 = s0 + m_buffer_read.size();
    setg( s0, s1, s2 );
  }
  else
  {
    setg( NULL, NULL, NULL );
  }

	return position;
}

t_memory_buffer::pos_type	t_memory_buffer::seekpos(pos_type position, std::ios_base::openmode which)
{
	return seekoff( position, std::ios::beg, which );
}

////////////////////////////////////////////////////////////////
//reset_for_read : resets the buffer and iterator for sequential reading
//from the buffer. Currently no support for random access.

void t_memory_buffer::reset_for_read()
{
	m_current_block		= m_buffers.begin();
	
	//if there is data to be copied to the current buffer do it now.
	if (m_current_block != m_buffers.end()) 
	{	
		reset_get_stream_ptrs( m_current_block->size() );
		
		memcpy( &(*m_buffer_read.begin()), &(*m_current_block->begin()), m_current_block->size() );
	}
	else
	{
		//empty buffer.

		reset_get_stream_ptrs( 0 );
	}

	return;
}

////////////////////////////////////////////////////////////////
//resize_working_buffer : resize the working buffers size.
void t_memory_buffer::resize_working_buffer( int request_mem_size )
{
	m_buffer_size_default = request_mem_size;

	m_buffer.resize( m_buffer_size_default );
	m_buffer_read.resize( m_buffer_size_default );
	
	reset_get_stream_ptrs(m_buffer_size_default);
	reset_put_stream_ptrs();
}

////////////////////////////////////////////////////////////////
//reset_get_stream_ptrs : resets the stream ptrs for the current block of 
//working memeory within m_buffer

void t_memory_buffer::reset_get_stream_ptrs( int size )
{
	m_buffer_read.resize( size );	

	// setg( m_buffer_read.begin(), m_buffer_read.begin(), m_buffer_read.end());

  if(m_buffer_read.size() > 0)
  {
    char*s0 = &(*m_buffer_read.begin());  
    char*s1 = s0 + m_buffer_read.size();
    setg( s0, s0, s1 );
  }
  else
    setg( 0, 0, 0 );
}

////////////////////////////////////////////////////////////////
//reset_put_stream_ptrs : resets the stream ptrs for the current block of 
//working memeory within m_buffer

void t_memory_buffer::reset_put_stream_ptrs( )
{
	// setp( m_buffer.begin(), m_buffer.begin(), m_buffer.end());

  if(m_buffer.size() > 0)
  {
    char*s0 = &(*m_buffer.begin());  
    char*s1 = s0 + m_buffer.size();
    setp( s0, s0, s1 );
  }
  else
    setp( 0, 0, 0 );  
}

// stream buf related class, which like stringbuffer goes to memory, which
// uses efficient allocation of buffers
// Virtual overrides
std::streambuf::int_type t_memory_buffer::sync()
{
	flush_buffer();
	return 0;
}

std::streambuf::int_type t_memory_buffer::underflow()
{
	//maintain iterator to the block which we are on currently.

	m_current_block++;
	
	if (m_current_block == m_buffers.end()) 
		return traits_type::eof();

	reset_get_stream_ptrs( m_current_block->size() );
	
	memcpy( &(*m_buffer_read.begin()), &(*m_current_block->begin()), m_current_block->size() );

	if ( egptr() > eback() )
		return traits_type::to_int_type( *gptr() );
	
	return traits_type::eof();
}

bool t_memory_buffer::read( std::streambuf& buffer )
{
	clear_buffer();

	t_uint32 buffer_size	= get< t_uint32 >( buffer );
	m_buffer_size_default	= get< t_uint32 >( buffer );

	for ( int i = 0; i < buffer_size ; i++ )
	{
		t_uint32 size = get< t_uint32 >( buffer );

		std::string data_buffer;

		data_buffer.resize( size );

		buffer.sgetn( &(*data_buffer.begin()), size );

		m_buffers.push_back( data_buffer );
	}
	
	m_buffer = "";

	reset_get_stream_ptrs( 0 );
	reset_put_stream_ptrs( );

	reset_for_read();

	return true;
}

bool t_memory_buffer::write( std::streambuf& buffer ) const 
{
	bool write_m_buffer = false;

	if (m_buffer.size() > 0)
		write_m_buffer = true;

	t_uint32 buffer_size = m_buffers.size();
	
	if (write_m_buffer == true)
		buffer_size += 1;

	put< t_uint32 >( buffer , buffer_size );
	put< t_uint32 >( buffer , m_buffer_size_default );

	if (write_m_buffer == true)
	{
		put< t_uint32 >( buffer, m_buffer.size() );
		
		buffer.sputn( &(*m_buffer.begin()), m_buffer.size() );
	}

	std::list<std::string>::const_iterator		buffer_iter;

	for ( buffer_iter = m_buffers.begin(); buffer_iter != m_buffers.end(); buffer_iter++ )
	{
		put< t_uint32 >( buffer, buffer_iter->size() );
		
		buffer.sputn( &(*buffer_iter->begin()) , buffer_iter->size() );
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////////
//dump_memory : differs from write() in that it writes the content without the header 
//information.

void t_memory_buffer::dump_memory( std::streambuf& buffer ) const 
{
	bool write_m_buffer = false;

	if (m_buffer.size() > 0)
		write_m_buffer = true;

	if (write_m_buffer == true)
	{
		buffer.sputn( &(*m_buffer.begin()) , m_buffer.size() );
	}

	std::list<std::string>::const_iterator		buffer_iter;

	for ( buffer_iter = m_buffers.begin(); buffer_iter != m_buffers.end(); buffer_iter++ )
	{	
		buffer.sputn( &(*buffer_iter->begin()) , buffer_iter->size() );
	}

	return ;
}

//////////////////////////////////////////////////////////////////////////////////
//counted version of the memory buffer used by dynamically allocated memory buffers
//ex. the replay event code.

t_memory_buffer_counted::t_memory_buffer_counted() : t_memory_buffer() 
{
}

t_memory_buffer_counted::t_memory_buffer_counted( int request_mem_size ) : t_memory_buffer ( request_mem_size )
{
}
	
t_memory_buffer_counted::~t_memory_buffer_counted()
{
}

t_memory_buffer_counted_ptr t_memory_buffer_counted::clone()
{
	t_memory_buffer_counted_ptr new_buffer = new t_memory_buffer_counted(m_buffer_size_default);

	//moves the current working data in the write buffer into the m_buffers
	flush_buffer();

	std::list<std::string>::const_iterator		buffer_iter;

	for ( buffer_iter = m_buffers.begin(); buffer_iter != m_buffers.end(); buffer_iter++ )
	{
		new_buffer->sputn( &(*buffer_iter->begin()), buffer_iter->size() );
	}

	//compact the buffers to remove excessive memory footprint used by the working buffer
	
	compact();

	new_buffer->compact();

	return new_buffer;
}
